package com.darkidiot.session;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;

/**
 * 分布式session实现
 * session attribute持久化到redis
 * Copyright (c) for darkidiot
 * Date:2017/4/14
 * Author: <a href="darkidiot@icloud.com">darkidiot</a>
 * School: CUIT
 * Desc:
 */
@Slf4j
class DistributedSession implements HttpSession {
    private volatile long lastAccessedAt;
    private final DistributedSessionManager sessionManager;
    private final String id;
    private final long createdAt;
    private final HttpServletRequest request;
    private int maxInactiveInterval;
    private final Map<String, Object> newAttributes = Maps.newHashMapWithExpectedSize(5);
    private final Set<String> deleteAttribute = Sets.newHashSetWithExpectedSize(5);
    private final Map<String, Object> dbSession;
    private volatile boolean invalid;
    private volatile boolean dirty;

    DistributedSession(DistributedSessionManager sessionManager, HttpServletRequest request, String id) {
        this.request = request;
        this.sessionManager = sessionManager;
        this.id = id;
        this.dbSession = loadDBSession();
        log.debug("load Attribute from Redis:{}", this.dbSession);
        this.createdAt = System.currentTimeMillis();
        this.lastAccessedAt = this.createdAt;
    }

    private Map<String, Object> loadDBSession() {
        return sessionManager.findAttributeByMsid(id);
    }

    @Override
    public long getCreationTime() {
        return createdAt;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public long getLastAccessedTime() {
        return lastAccessedAt;
    }

    @Override
    public ServletContext getServletContext() {
        return request.getServletContext();
    }

    @Override
    public void setMaxInactiveInterval(int interval) {
        maxInactiveInterval = interval;
    }

    @Override
    public int getMaxInactiveInterval() {
        return maxInactiveInterval;
    }

    @Override
    @Deprecated
    public HttpSessionContext getSessionContext() {
        return null;
    }

    @Override
    public Object getAttribute(String name) {
        checkValid();
        if (newAttributes.containsKey(name)) {
            log.debug("find the attribute: [{}={}] from LocalCache", name, newAttributes.get(name));
            return newAttributes.get(name);
        }
        if (deleteAttribute.contains(name)) {
            log.debug("can not find the attribute:[{}] from LocalCache has been logical deleted.", name);
            return null;
        }
        log.debug("find the attribute: [{}={}] from RedisCache", name, dbSession.get(name));
        return dbSession.get(name);
    }

    @Deprecated
    @Override
    public Object getValue(String name) {
        return getAttribute(name);
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        checkValid();
        Set<String> names = Sets.newHashSet(dbSession.keySet());
        names.addAll(newAttributes.keySet());
        names.removeAll(deleteAttribute);
        Enumeration<String> enumeration = Collections.enumeration(names);
        log.debug("find the attribute Names: [{}] from LocalCache", enumeration);
        return enumeration;
    }

    @Deprecated
    @Override
    public String[] getValueNames() {
        checkValid();
        Set<String> names = Sets.newHashSet(dbSession.keySet());
        names.addAll(newAttributes.keySet());
        names.removeAll(deleteAttribute);
        String[] valueNames = names.toArray(new String[0]);
        log.debug("find the attribute ValueNames: [{}] from LocalCache", Arrays.toString(valueNames));
        return valueNames;
    }

    @Override
    public void setAttribute(String name, Object value) {
        checkValid();
        if (value != null) {
            newAttributes.put(name, value);
            deleteAttribute.remove(name);
        } else {
            deleteAttribute.add(name);
            newAttributes.remove(name);
        }
        log.debug("set the attribute Names: [{}={}] into LocalCache", name, value);
        dirty = true;
    }

    @Deprecated
    @Override
    public void putValue(String name, Object value) {
        setAttribute(name, value);
    }

    @Override
    public void removeAttribute(String name) {
        checkValid();
        deleteAttribute.add(name);
        newAttributes.remove(name);
        log.debug("remove the attribute Names: [{}] from LocalCache", name);
        dirty = true;
    }

    @Deprecated
    @Override
    public void removeValue(String name) {
        removeAttribute(name);
    }

    @Override
    public void invalidate() {
        invalid = true;
        dirty = true;
        log.debug("invalidate this session: [id={}]", id);
        sessionManager.deletePhysically(getId());
    }

    @Override
    public boolean isNew() {
        return true;
    }

    private void checkValid() throws IllegalStateException {
        Preconditions.checkState(!invalid, "session is invalid.");
    }

    boolean isDirty() {
        return dirty;
    }

    Map<String, Object> snapshot() {
        Map<String, Object> snap = Maps.newHashMap();
        snap.putAll(dbSession);
        snap.putAll(newAttributes);
        for (String name : this.deleteAttribute) {
            snap.remove(name);
        }
        log.debug("snapshot this session attribute: {}",snap);
        return snap;
    }

    boolean isValid() {
        return !invalid;
    }
}
